
/**
 * Extends a HTML DOM element with an ID attribute to an entity view element.
 * 
 * @class
 * @param {E.Dom}
 *          entityViewDom DOM representing the entity.
 */
EntityView = function(entityViewDom) {
	this.dom = entityViewDom;
	if (!this.dom.entityView)
		this.dom.entityView = this;
	EntityView.parseIdAttrValue(this.dom.element, this);
	if (!this.objId) {
		var e = this.dom.element;
		while (e = e.parentNode) {
			var v = EntityView.parseIdAttrValue(e);
			if (v && v.objId) {
				this.objId = v.objId;
				this.scope = v.scope;
				break;
			}
		}
	}
	if (!this.objId)
		alert("No object ID found for entity view '" + this.dom.element.id + "'");
	if (this.args) {
		// this is a place holder for a new entity
		// for now the parent entity view is also the parent entity for the
		// placeholder
		var args = this.args;
		var p = this.getParent();
		while (p.args) {
			args = p.args + ":" + args;
			p = p.getParent();
		}
		this.tmplParent = p;
		this.entity = new Entity(p.objId, null, p.scope);
		this.entity.parentId = p.id;
		this.entity.tmpl = {
			objId : this.objId,
			id : this.id,
			args : args.split(":")
		};
	}
	else {
		this.entity = new Entity(this.objId, this.id, this.scope);
	}
	this.v = this.dom.getVector();
	this.s = this.dom.getSize();
}

EntityView.parseIdAttrValue = function(e, r) {
	if (!e || !e.id || !e.id.indexOf)
		return;
	var v = e.id;
	// hidden ID
	if (v.indexOf("_") == 0)
		return;
	if (!r)
		r = new Object();
	// place holder
	var p = v.indexOf(":");
	if (p >= 0) {
		r.args = v.substring(p + 1);
		v = v.substring(0, p);
	}
	// view num
	p = v.indexOf("~");
	if (p >= 0)
		v = v.substring(0, p);
	// node ID, object ID
	p = v.indexOf("#");
	if (p >= 0)
		r.id = v.substring(p + 1);
	else if (v.indexOf("/") < 0)
		r.id = v;
	else if (v.length > 0)
		r.objId = v;
	if (p > 0)
		r.objId = v.substring(0, p);
	if (r.objId) {
		var accessScopes = ["content", "meta", "meta2", "cosmos", "history", "view", "data", "cache"];
		for (var i = 0; i < accessScopes.length; ++i) {
			var s = accessScopes[i];
			p = r.objId.lastIndexOf("." + s);
			if ((p > 0) && ((p + s.length + 1) == r.objId.length)) {
				r.scope = s;
				r.objId = r.objId.substring(0, p);
				break;
			}
		}
	}
	return r;
}

EntityView.prototype.applyEditMode = function(inEditMode) {
	var e = this.dom.element;
	e.style.cursor = (inEditMode)?"pointer":"";
}

EntityView.prototype.getViewObjId = function() {
	var viewObjId = this.dom.element.ownerDocument.documentElement.id;
	if (!viewObjId) {
		viewObjId = this.dom.element.ownerDocument.location.href.substring(BasePath.get().length);
		var p = viewObjId.indexOf("?");
		if (0 < p)
			viewObjId = viewObjId.substring(0, p);
		p = viewObjId.lastIndexOf(".");
		if (0 < p)
			viewObjId = viewObjId.substring(0, p);
	}
	return viewObjId;
}

// Selects this entity view element for editing.
// @param active Boolean to select/unselect.
// @return -
EntityView.prototype.selectForEdit = function(active, scrollIntoView) {
	this.isSelected = active;
	var paper = this.dom.element.getElementsByTagName("div")[0];
	if (paper && ("paper" == paper.className))
		this.markElement(Dom(paper), active, "ed_select");
	else
		this.markElement(this.dom, active, "ed_select");
	if (active && ((typeof(scrollIntoView) == "undefined") || scrollIntoView))
			this.dom.scrollIntoView();
}

// Marks this entity view element for being processed.
// @param doMark Boolean to mark/unmark.
// @return -
EntityView.prototype.markInProcess = function(doMark) {
	this.markElement(this.dom, doMark, "ed_process");
}

// Marks an element.
// @param element Element to mark.
// @param doMark Boolean to mark/unmark.
// @return -
EntityView.prototype.markElement = function(dom, doMark, style) {
	if (doMark) {
		dom.addStyleClass(style);
		// make sure the edit selection get visible (if the element's original
		// style already has a background color, it might be masking the
		// ed_selection
		// style background color.
		if (style == "ed_select")
			dom.element.style.backgroundColor = "#DDDDEE";
		else if (style == "ed_process")
			dom.element.style.backgroundColor = "#EEEEEE";
	}
	else {
		dom.element.style.backgroundColor = "";
		dom.removeStyleClass(style);
	}
	for (var i = 0; i < dom.element.childNodes.length; ++i) {
		var e = dom.element.childNodes[i];
		if ((e.nodeType == 1) && !EntityView.isEntityViewElement(e))
			this.markElement(Dom(e), doMark, style);
	}
}

// Access to this parent entity view element.
// @return The parent entity view element or 'null', if no parent is available.
EntityView.prototype.getParent = function() {
	var parent = this.dom.element.parentNode;
	while (parent) {
		if (EntityView.isEntityViewElement(parent))
			return Dom(parent).getEntityView();
		parent = parent.parentNode;
	}
	return null;
}

// Access to this first child entity view element.
// @return The first child entity view element or 'null', if no child is
// available.
EntityView.prototype.getFirstChild = function() {
	return EntityView.getFirstChildEntityView(this.dom.element);
}

EntityView.getFirstChildEntityView = function(node) {
	var children = node.childNodes;
	for (var i = 0; i < children.length; i++) {
		var child = children[i];
		if (EntityView.isEntityViewElement(child))
			return Dom(child).getEntityView();
		child = EntityView.getFirstChildEntityView(child);
		if (child)
			return child;
	}
	return null;
}

// Access to this last child entity view element.
// @return The last child entity view element or 'null', if no child is
// available.
EntityView.prototype.getLastChild = function() {
	return EntityView.getLastChildEntityView(this.dom.element);
}

EntityView.getLastChildEntityView = function(node) {
	var children = node.childNodes;
	for (var i = children.length - 1; i >= 0; i--) {
		var child = children[i];
		if (EntityView.isEntityViewElement(child))
			return Dom(child).getEntityView();
		child = EntityView.getLastChildEntityView(child);
		if (child)
			return child;
	}
	return null;
}

// Access to the child entity view elements.
// @return The child entity view elements or 'null', if no child is available.
EntityView.prototype.getChildren = function() {
	var entityViews = new Array();
	EntityView._getChildEntityViews(this.dom.element, entityViews);
	return entityViews;
}

EntityView._getChildEntityViews = function(node, entityViews) {
	var children = node.childNodes;
	for (var i = 0; i < children.length; i++) {
		var child = children[i];
		if (EntityView.isEntityViewElement(child))
			entityViews[entityViews.length] = Dom(child).getEntityView();
		else
			EntityView._getChildEntityViews(child, entityViews);
	}
}

// Access to this previous sibling entity view element.
// @return The previous sibling entity view element or 'null', if no previous
// sibling is available.
EntityView.prototype.getPreviousSibling = function() {
	var sibling = this.dom.element.previousSibling;
	while (sibling) {
		if (EntityView.isEntityViewElement(sibling))
			return Dom(sibling).getEntityView();
		sibling = sibling.previousSibling;
	}
	var parentEntityView = this.getParent();
	if (null == parentEntityView)
		return null;
	sibling = null;
	var childEntityViews = parentEntityView.getChildren();
	for (var i = 0; i < childEntityViews.length; ++i) {
		var c = childEntityViews[i];
		if (c == this)
			return sibling;
		sibling = c;
	}
	return null;
}

// Access to this next sibling entity view element.
// @return The next sibling entity view element or 'null', if no next sibling is
// available.
EntityView.prototype.getNextSibling = function() {
	var sibling = this.dom.element.nextSibling;
	while (sibling) {
		if (EntityView.isEntityViewElement(sibling))
			return Dom(sibling).getEntityView();
		sibling = sibling.nextSibling;
	}
	var parentEntityView = this.getParent();
	if (null == parentEntityView)
		return null;
	var childEntityViews = parentEntityView.getChildren();
	sibling = childEntityViews[0];
	for (var i = 1; i < childEntityViews.length; ++i) {
		var c = childEntityViews[i];
		if (sibling == this)
			return c;
		sibling = c;
	}
	return null;
}

EntityView.prototype.getPrevious = function() {
	var ev = this.getPreviousSibling();
	while (null != ev) {
		var c = ev.getChildren();
		if (c.length > 0)
			ev = c[c.length - 1];
		else
			return ev;
	}
	return this.getParent();
}

EntityView.prototype.getNext = function() {
	var n = this.getFirstChild();
	if (null != n)
		return n;
	n = this.getNextSibling();
	if (null != n)
		return n;
	var p = this.getParent();
	while (null != p) {
		n = p.getNextSibling();
		if (null != n)
			return n;
		p = p.getParent();
	}
	return null;
}

EntityView.getEntityViews = function() {
	if (!EntityView.entityViews) {
		EntityView.entityViews = new Array();
		var es = Doc().contentPanel.element.getElementsByTagName("*");
		for (var i = 0; i < es.length; ++i) {
			var e = es[i];
			if (!EntityView.isEntityViewElement(e))
				continue;
			var ev = Dom(e).getEntityView();
			EntityView.entityViews.push(ev);
		}
	}
	return EntityView.entityViews;
}

// get the top most centered entity view currently visible (scrolling)
EntityView.getVisualTopCenter = function(evs) {
	if (!evs)
		evs = EntityView.getEntityViews();
	var b = Dom(document.body);
	var sl = b.getScrollVector();
	var sz = [window.innerWidth, window.innerHeight];
	// the visual center (a 5th to the top, a 3rd to the left)
	var vC = [sl[0] + sz[0] / 3, sl[1] + sz[1] / 5];
	var dMin = Number.MAX_VALUE;
	var evC = null;
	for (var i = 0; i < evs.length; ++i) {
		var ev = evs[i];
		if ((ev.v[0] > (sl[0] + sz[0])) || ((ev.v[0] + ev.s[0]) < sl[0]) || (ev.v[1] > (sl[1] + sz[1]))
			|| ((ev.v[1] + ev.s[1]) < sl[1]))
			// ignore invisible views
			continue;
		var v = [ev.v[0] + ev.s[0] / 2, ev.v[1] + ev.s[1] / 2];
		var vD = [v[0] - vC[0], v[1] - vC[1]];
		var d = Math.sqrt(Math.pow(vD[0], 2) + Math.pow(vD[1], 2));
		// prefer line based views
		d = d - ev.s[0] / ev.s[1];
		// prefer medium sized views (50 kPixel; log(50) = 1.7)
		var a = ev.s[0] * ev.s[1] / 1000;
		d = d + Math.pow((Math.log(a) / Math.LN10 - 1.7), 2) * 100;
		if (d < dMin) {
			evC = ev;
			dMin = d;
		}
	}
	return evC;
}

// get the top most centered entity view currently visible (scrolling)
EntityView.getFirstEntityViewById = function(evs, objId, id) {
	if (!evs)
		evs = EntityView.getEntityViews();
	for (var i = 0; i < evs.length; ++i) {
		var ev = evs[i];
		if ((ev.entity.objId == objId) && (ev.entity.id == id))
			return ev;
	}
	return null;
}

EntityView.prototype.getLeft = function(evs, vFactor) {
	if (!evs)
		evs = EntityView.getEntityViews();
	if (!vFactor)
		vFactor = 1;
	var dMin = -1;
	var evC = null;
	for (var i = 0; i < evs.length; ++i) {
		var ev = evs[i];
		if (ev == this)
			continue;
		var d = this.v[0] - ev.v[0] - ev.s[0];
		if (0 > d)
			continue;
		d += Math.abs(this.v[1] - ev.v[1]) * vFactor;
		if ((0 > dMin) || (d < dMin)) {
			evC = ev;
			dMin = d;
		}
	}
	return evC;
}

EntityView.prototype.getRight = function(evs, vFactor) {
	if (!evs)
		evs = EntityView.getEntityViews();
	if (!vFactor)
		vFactor = 1;
	var dMin = -1;
	var evC = null;
	for (var i = 0; i < evs.length; ++i) {
		var ev = evs[i];
		if (ev == this)
			continue;
		var d = ev.v[0] - this.v[0] - this.s[0];
		if (0 > d)
			continue;
		d += Math.abs(this.v[1] - ev.v[1]) * vFactor;
		if ((0 > dMin) || (d < dMin)) {
			evC = ev;
			dMin = d;
		}
	}
	return evC;
}

EntityView.prototype.getUp = function(evs, hFactor) {
	if (!evs)
		evs = EntityView.getEntityViews();
	if (!hFactor)
		hFactor = 1;
	var dMin = -1;
	var evC = null;
	for (var i = 0; i < evs.length; ++i) {
		var ev = evs[i];
		if (ev == this)
			continue;
		var d = this.v[1] - ev.v[1];// - ev.s[1];
		if ((1 > d) && !ev.dom.isAncestorOf(this.dom.element))
			continue;
		d += Math.abs(this.v[0] - ev.v[0]) * hFactor;
		if ((0 > dMin) || (d <= dMin)) {
			evC = ev;
			dMin = d;
		}
	}
	return evC;
}

EntityView.prototype.getDown = function(evs, hFactor) {
	if (!evs)
		evs = EntityView.getEntityViews();
	if (!hFactor)
		hFactor = 1;
	var dMin = -1;
	var evC = null;
	for (var i = 0; i < evs.length; ++i) {
		var ev = evs[i];
		if (ev == this)
			continue;
		var isChild = ev.dom.hasAncestor(this.dom.element);
		var d = ev.v[1] - this.v[1];// - this.s[1];
		if ((1 > d) && !isChild)
			continue;
		if (!isChild)
			d += this.s[0] + this.s[1];
		d += Math.abs(this.v[0] - ev.v[0]) * hFactor;
		if ((0 > dMin) || (d < dMin)) {
			evC = ev;
			dMin = d;
		}
	}
	return evC;
}

// TODO: rename
EntityView.prototype.contains = function(element) {
	return this.dom.element == EntityView.getEntityViewElement(element);
}

// Builds the markup for the entity view ancestor path for this entity view.
// @param fnName the name of the JavaScript function to call, when an ancestor
// has been clicked.
EntityView.prototype.getActiveEntityPath = function() {
	var entityView = this;
	var path = "<span onclick=\"Doc().selectionSupport.select(Dom('" + entityView.dom.element.id
		+ "').getEntityView())\" class=\"link\" style=\"color: white\">" + entityView.dom.element.id + "</span>";
	while ((entityView.dom.element.className != "contentPanel")
		&& (entityView.dom.element.tagName != "body")) {
		entityView = entityView.getParent();
		if (!entityView)
			break;
		if ((entityView.dom.element.className == "contentPanel")
			|| (entityView.dom.element.tagName == "body"))
			path += "<br/>";
		path = "<span onclick=\"Doc().selectionSupport.select(Dom('" + entityView.dom.element.id
			+ "').getEntityView())\" class=\"link\">" + entityView.dom.element.id + "</span> - " + path;
	}
	return path;
}

// Check a node to be an entity view element.
// @param node The node to be checked.
// @return Boolean to result the check.
EntityView.isEntityViewElement = function(node) {
	// an entity view element holds the id of the represented entity.
	return (typeof(node.id) != "undefined") && (node.id.length > 0) && (node.id.charAt(0) != '_')
		&& (node != node.ownerDocument.documentElement)
		// try to keep out the dummy elements from plugins and stuff...
		&& (!node.namespaceURI || (node.namespaceURI == "http://www.w3.org/1999/xhtml")
				|| (node.namespaceURI == "http://www.w3.org/2000/svg"));
}

// Determines the entity view element for a given node. Its the first entity
// view element along the
// ancestor axis.
// @param node The node to determine the entity view element for.
// @return The entity view element containing the given node.
EntityView.getEntityViewElement = function(node) {
	while (node) {
		if (EntityView.isEntityViewElement(node))
			return node;
		node = node.parentNode;
	}
	return null;
}

E.Dom.prototype.getEntityView = function() {
	if (!this.entityView) {
		var evEl = EntityView.getEntityViewElement(this.element);
		if (evEl) {
			var d = Dom(evEl);
			if (evEl.namespaceURI == "http://www.w3.org/2000/svg")
				d.entityView = new SvgEntityView(d);
			else
				d.entityView = new EntityView(d);
			this.entityView = d.entityView;
		}
	}
	return this.entityView;
}

/**
 * Extends a SVG DOM element with an ID attribute to an entity view element.
 * 
 * @class
 * @param {E.Dom}
 *          entityViewDom DOM representing the entity.
 */
SvgEntityView = function(entityViewDom) {
	SvgEntityView._super.call(this, entityViewDom);
}
Object.extend("SvgEntityView", "EntityView");

SvgEntityView.getPos = function(e) {
	var b = e.getBBox();
	var ctm = e.getCTM();
	var pCtm = e.parentNode.getCTM();
	return [-pCtm.e + ctm.e + b.width/2 + b.x, -pCtm.f + ctm.f + b.height/2 + b.y];
}
SvgEntityView.prototype.applyEditMode = function(inEditMode) {
	SvgEntityView._super.prototype.applyEditMode.call(this, inEditMode);
	var e = this.dom.element;
	if (this.dom.containsStyleClass("box")) {
	}
	else if (this.dom.containsStyleClass("lineTo")) {
		var p = e.parentNode;
		if (Dom(p)._editModeApplied)
			return;
		Dom(p)._editModeApplied = inEditMode;
		if (inEditMode) {
			p.onmouseover = function(evn) {
				var pts = this.getElementsByTagName("circle");
				for (var i = 0; i < pts.length; i++) {
					var pt = pts[i];
					var fill = (Dom(pt).getEntityView().entity.tmpl)?"#BBBBBB":"#888877";
					pt.setAttributeNS(null, "fill", fill);
				}
			}
			p.onmouseout = function(evn) {
				var pts = this.getElementsByTagName("circle");
				for (var i = 0; i < pts.length; i++) {
					var pt = pts[i];
					if (Dom(pt).getEntityView().isSelected)
						return;
				}
				for (var i = 0; i < pts.length; i++) {
					var pt = pts[i];
					pt.setAttributeNS(null, "fill", "none");
				}
			}
		}
		else {
			var pts = p.getElementsByTagName("circle");
			for (var i = 0; i < pts.length; i++) {
				var pt = pts[i];
				pt.setAttributeNS(null, "fill", "none");
			}
			p.onmouseover = null;
			p.onmouseout = null;
		}
	}
}

/**
 * Selects this entity view element for editing.
 * 
 * @param {Boolean}
 *          active Select/unselect.
 * @param {Boolean}
 *          scrollIntoView
 */
SvgEntityView.prototype.selectForEdit = function(active, scrollIntoView) {
	SvgEntityView._super.prototype.selectForEdit.call(this, active, scrollIntoView);
	this.isSelected = active;
	if (this.dragSource)
		this.dragSource.release();
	this.dragSource = null;
	if (active) {
		if (this.dom.containsStyleClass("box")) {
			this.dragSource = new E.DragMoveSource(this.dom, this.dom.element);
			this.pos = SvgEntityView.getPos(this.dom.element);
			var as = this.dom.element.getElementsByTagNameNS("http://www.w3.org/2000/svg", "a");
			for (var i = 0; i < as.length; i++)
				as[i].style.cursor = "move";
			var ev = this;
			this.dragSource.dropHandler = function(dragView, v, evn) {
				var p = [v[0] + ev.pos[0], v[1] + ev.pos[1]];
				var action = new UpdateValueAction(ev, p, "@pos");
				action.perform();
				this.release();
				ev.dragSource = null;
				return true;
			};
		}
		else if (this.dom.containsStyleClass("lineTo")) {
			this.dragSource = new E.DragMoveSource(this.dom, this.dom.element);
			this.pos = SvgEntityView.getPos(this.dom.element);
			var ev = this;
			this.dragSource.dragHandler = function(dragView, v, evn) {
				var e = ev.dom.element;
				e.style.cursor = "default";
				return true;
			};
			this.dragSource.dropHandler = function(dragView, v, evn) {
				var p = [v[0] + ev.pos[0], v[1] + ev.pos[1]];
				var action;
				var tmpl = ev.entity.tmpl;
				if (tmpl) {
					var refEv = ev.tmplParent;
					var into = true;
					if (refEv.dom.containsStyleClass("path")) {
						var sEv = ev.getNextSibling();
						if (sEv) {
							refEv = sEv;
							into = false;
						}
						tmpl.args[1] = p;
					}
					else {
						var e = ev.dom.element;
						var siblings = Dom(e.parentNode).getElementsByClassName("lineTo");
						for (var i = 0; i < siblings.length; i++) {
							if (siblings[i] == e) {
								tmpl.args[i + 1] = p;
								break;
							}
						}
					}
					action = new InsertAction(refEv, tmpl, true);
					action.into = into;
				}
				else {
					action = new UpdateValueAction(ev, p, "@to");
				}
				action.perform();
				this.release();
				ev.dragSource = null;
				ev.dom.element.removeAttributeNS(null, "transform");
				return true;
			};
		}
	}
	else {
		if (this.dom.containsStyleClass("box")) {
			var as = this.dom.element.getElementsByTagNameNS("http://www.w3.org/2000/svg", "a");
			for (var i = 0; i < as.length; i++)
				as[i].style.cursor = null;
		}
		else if (this.dom.containsStyleClass("lineTo")) {
			var p = this.dom.element.parentNode;
			if (p) {
				var pts = p.getElementsByTagName("circle");
				for (var i = 0; i < pts.length; i++) {
					var pt = pts[i];
					pt.setAttributeNS(null, "fill", "none");
				}
			}
		}
	}
}

// Marks an element.
// @param element Element to mark.
// @param doMark Boolean to mark/unmark.
// @return -
SvgEntityView.prototype.markElement = function(dom, doMark, style) {
	SvgEntityView._super.prototype.markElement.call(this, dom, doMark, style);
	if (doMark) {
		if (style == "ed_select") {
			if (this.dom.containsStyleClass("path")) {
				if (!dom._selectBox) {
					var r = dom.element.childNodes[0].cloneNode(true);
					dom._selectBox = r;
					r.setAttributeNS(null, "stroke", "#DDDDEE");
					r.setAttributeNS(null, "stroke-width", "7");
					dom.element.insertBefore(r, dom.element.childNodes[1]);
				}
			}
			else if (typeof(dom.element.getBBox) == "function") {
				if (!dom._selectBox) {
					var p = dom.element.parentNode;
					var b = dom.element.getBBox();
					var r = dom.element.ownerDocument.createElementNS("http://www.w3.org/2000/svg", "rect");
					dom._selectBox = r;
					r.setAttributeNS(null, "style", "fill: #DDDDEE;");
					r.setAttributeNS(null, "x", b.x - 4);
					r.setAttributeNS(null, "y", b.y - 4);
					r.setAttributeNS(null, "width", b.width + 8);
					r.setAttributeNS(null, "height", b.height + 8);
					r.setAttributeNS(null, "transform", dom.element.getAttributeNS(null, "transform"));
					p.insertBefore(r, dom.element);
				}
				if (this.dom.containsStyleClass("lineTo"))
					dom.element.setAttributeNS(null, "fill", (this.entity.tmpl)?"#AAAA99":"#888877");
			}
		}
	}
	else {
		if (dom._selectBox) {
			if (dom._selectBox.parentNode)
				dom._selectBox.parentNode.removeChild(dom._selectBox);
			dom._selectBox = null;
		}
	}
}
